<?php

namespace App\Admin\Grid;

use App\Admin\Grid;
use App\Admin\Grid\Displayers\AbstractDisplayer;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Str;

class Column
{
    /**
     * 表格Grid实例
     *
     * @var Grid
     */
    protected $grid;
    /**
     * 列的字段名
     *
     * @var
     */
    protected $name;

    /**
     * 表格显示的列名
     *
     * @var mixed
     */
    protected $label;

    /**
     * 该列的原始数据
     *
     * @var
     */
    protected $original;

    /**
     * 该列是否可以排序
     *
     * @var bool
     */
    protected $sortable = false;

    /**
     * 排序的参数
     *
     * @var array
     */
    protected $sort;

    /**
     * 列属性
     *
     * @var array
     */
    protected $attributes = [];

    /**
     * 关联名
     *
     * @var bool
     */
    protected $relation = false;

    /**
     * 关联列
     *
     * @var string
     */
    protected $relationColumn;

    /**
     * 表格的原始数据
     *
     * @var array
     */
    protected static $originalGridData = [];

    /**
     * 表格数据显示时要做处理的回调
     *
     * @var \Closure
     */
    protected $displayCallbacks = [];

    /**
     * 列的Displayers
     *
     * @var array
     */
    public static $displayers = [];

    /**
     * 已定义的列
     *
     * @var array
     */
    public static $defined = [];

    /**
     * 列宽度
     *
     * @var mixed
     */
    protected $width;

    /**
     * Column constructor.
     * @param $name
     * @param $label
     */
    public function __construct($name, $label)
    {
        $this->name = $name;
        $this->label = $this->formatLabel($label);
    }

    /**
     * 处理表格中列的名称
     *
     * @param $label
     * @return mixed
     */
    protected function formatLabel($label)
    {
        $label = $label ?: ucfirst($this->name);

        return str_replace(['.', '_'], ' ', $label);
    }

    /**
     * 返回列的字段名
     *
     * @return mixed
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * 返回列的显示名称
     *
     * @return mixed
     */
    public function getLabel()
    {
        return $this->label;
    }

    /**
     * 给该列的值添加一个显示前的回调
     *
     * @param \Closure $callable
     * @return $this
     */
    public function value(\Closure $callable)
    {
        return $this->display($callable);
    }

    /**
     * 设置该列为可排序列
     *
     * @return $this
     */
    public function sortable()
    {
        $this->sortable = true;

        return $this;
    }

    /**
     * 生成表格中用来显示排序的html代码
     *
     * @return void|string
     */
    public function sorter()
    {
        if (!$this->sortable) {
            return;
        }

        // 当前排序类型的指示图标
        $icon = 'sort';
        // 点击排序链接将要切换的排序类型
        $type = 'asc';

        // 排序切换顺序为:默认排序 => 从小到大 => 从大到小 => 默认排序
        if ($this->isSorted()) {
            $type = $this->sort['type'] == 'desc' ? '' : 'desc';
            $icon = $this->sort['type'] == 'desc' ? 'sort-down' : 'sort-up';
        }

        $query = \Request::all();

        if ($type) {
            $query = array_merge($query, [$this->grid->model()->getSortName() => ['column' => $this->name, 'type' => $type]]);
        } else {
            array_forget($query, $this->grid->model()->getSortName());
        }

        $queryString = http_build_query($query);
        $url = \URL::current() . ($queryString ? ('?' . $queryString) : '');

        return "<a href='{$url}'> <i class='grid-sort fas fa-{$icon}'></i></a>";
    }

    /**
     * 判断该列是不是排序列
     *
     * @return bool
     */
    protected function isSorted()
    {
        $this->sort = \Request::get($this->grid->model()->getSortName());

        if (empty($this->sort)) {
            return false;
        }

        return isset($this->sort['column']) && $this->sort['column'] == $this->name;
    }

    /**
     * 判断该列是不是关联列
     *
     * @return bool
     */
    protected function isRelation()
    {
        return (bool)$this->relation;
    }

    /**
     * 调用displayer方法
     *
     * @param $abstract
     * @param $arguments
     * @return mixed
     */
    protected function resolveDisplayer($abstract, $arguments)
    {
        if (array_key_exists($abstract, static::$displayers)) {
            return $this->callBuiltinDisplayer(static::$displayers[$abstract], $arguments);
        }

        return $this->callSupportDisplayer($abstract, $arguments);
    }

    /**
     * 调用其他laravel里的数组或字符串处理函数
     *
     * @param $abstract
     * @param $arguments
     * @return Column
     */
    protected function callSupportDisplayer($abstract, $arguments)
    {
        return $this->display(function ($value) use ($abstract, $arguments) {
            if (is_array($value) || $value instanceof Arrayable) {
                // 把原始数据$value转成Collection,再调用集合的方法处理
                return call_user_func_array([collect($value), $abstract], $arguments);
            }

            if (is_string($value)) {
                return call_user_func_array([Str::class, $abstract], array_merge([$value], $arguments));
            }

            return $value;
        });
    }

    /**
     * 调用内建的displayer方法
     *
     * @param $abstract
     * @param $arguments
     * @return mixed
     */
    protected function callBuiltinDisplayer($abstract, $arguments)
    {
        if ($abstract instanceof \Closure) {
            return $this->display(function ($value) use ($abstract, $arguments) {
                return call_user_func_array(
                    $abstract->bindTo($this),
                    array_merge([$value], $arguments)
                );
            });
        }

        if (class_exists($abstract) && is_subclass_of($abstract, AbstractDisplayer::class)) {
            $grid = $this->grid;
            $column = $this;

            return $this->display(function ($value) use ($abstract, $grid, $column, $arguments) {
                $displayer = new $abstract($value, $grid, $column, $this);

                return call_user_func_array([$displayer, 'display'], $arguments);
            });
        }

        return $this;
    }

    /**
     * 添加一个数据显示前的处理回调
     *
     * @param \Closure $callback
     * @return $this
     */
    public function display(\Closure $callback)
    {
        $this->displayCallbacks[] = $callback;

        return $this;
    }

    /**
     * 调用内建或者laravel的数组和字符串处理方法,来处理要显示的数据
     *
     * @param $method
     * @param $arguments
     * @return $this|mixed
     */
    public function __call($method, $arguments)
    {
        if ($this->isRelation() && !$this->relationColumn) {
            $this->name = "{$this->relation}.$method";
            $this->label = isset($arguments[0]) ? $arguments[0] : ucfirst($method);

            $this->relationColumn = $method;

            return $this;
        }

        return $this->resolveDisplayer($method, $arguments);
    }

    /**
     * 把回调函数内部的$this绑定到某一行的数据
     *
     * @param \Closure $callback
     * @param int      $key
     * @return \Closure
     */
    protected function bindOriginalRow(\Closure $callback, $key)
    {
        $originalRow = static::$originalGridData[$key];

        return $callback->bindTo((object)$originalRow);
    }

    /**
     * 设置表格的原始数据
     *
     * @param array $input
     */
    public static function setOriginalGridData(array $input)
    {
        static::$originalGridData = $input;
    }

    /**
     * 对该列的值,执行所有的回调处理
     *
     * @param mixed $value
     * @param int   $key
     * @return mixed
     */
    protected function callDisplayCallbacks($value, $key)
    {
        foreach ($this->displayCallbacks as $callback) {
            $callback = $this->bindOriginalRow($callback, $key);
            $value = call_user_func($callback, $value);
        }

        return $value;
    }

    /**
     * 重新定义列的回调处理方法,清除其他所有的方法
     *
     * @param string $name
     * @param mixed  $definition
     */
    public static function define($name, $definition)
    {
        static::$defined[$name] = $definition;
    }

    /**
     * 扩展一个自定义的Displayer
     *
     * @param $name
     * @param $displayer
     */
    public static function extend($name, $displayer)
    {
        static::$displayers[$name] = $displayer;
    }

    /**
     * 处理该列的数据,保存原始值,逐个用回调处理
     *
     * @param array $data
     * @return array
     * @throws \Exception
     */
    public function fill(array $data)
    {
        foreach ($data as $key => &$row) {
            $this->original = $value = array_get($row, $this->name);

            $value = $this->htmlEntityEncode($value);

            array_set($row, $this->name, $value);

            if ($this->isDefinedColumn()) {
                $this->useDefinedColumn();
            }

            if ($this->hasDisplayCallbacks()) {
                $value = $this->callDisplayCallbacks($this->original, $key);
                array_set($row, $this->name, $value);
            }
        }

        return $data;
    }

    /**
     * 判断是否有显示前需要调用的回调
     *
     * @return bool
     */
    protected function hasDisplayCallbacks()
    {
        return !empty($this->displayCallbacks);
    }

    /**
     * 转义处理数据中的html代码
     *
     * @param $item
     * @return array|string
     */
    protected function htmlEntityEncode($item)
    {
        if (is_array($item)) {
            array_walk_recursive($item, function (&$value) {
                $value = htmlentities($value);
            });
        } else {
            $item = htmlentities($item);
        }

        return $item;
    }

    /**
     * 判断该列是不是需要重新设定display回调
     *
     * @return bool
     */
    protected function isDefinedColumn()
    {
        return array_key_exists($this->name, static::$defined);
    }

    /**
     * 用该列在$defined数组中的display回调替换掉该列之前所有的回调
     *
     * @throws \Exception
     */
    protected function useDefinedColumn()
    {
        // 清除所有display回调,并用$defined数组中的代替
        $this->displayCallbacks = [];

        $class = static::$defined[$this->name];

        if ($class instanceof \Closure) {
            $this->display($class);

            return;
        }

        if (!class_exists($class) || !is_subclass_of($class, AbstractDisplayer::class)) {
            throw new \Exception("Invalid column definition [$class]");
        }

        $grid = $this->grid;
        $column = $this;

        $this->display(function ($value) use ($grid, $column, $class) {
            /**
             * @var AbstractDisplayer $definition
             */
            $definition = new $class($value, $grid, $column, $this);

            return $definition->display();
        });
    }

    /**
     * 绑定Grid实例
     *
     * @param Grid $grid
     */
    public function setGrid(Grid $grid)
    {
        $this->grid = $grid;
    }

    /**
     * 设置该列的关联
     *
     * @param string $relation
     * @param string $relationColumn
     *
     * @return $this
     */
    public function setRelation($relation, $relationColumn = null)
    {
        $this->relation = $relation;
        $this->relationColumn = $relationColumn;

        return $this;
    }

    /**
     * 设置或者获取列宽
     *
     * @param null $width
     * @return $this|mixed
     */
    public function width($width = null)
    {
        if (is_null($width)) {
            return $this->width;
        }

        $this->width = $width;

        return $this;
    }

    /**
     * 返回该列是否可排序
     *
     * @return bool
     */
    public function isSortable()
    {
        return $this->sortable;
    }
}